// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: leon

#pragma once

#include <deque>
#include <map>
#include <optional>
#include <sstream>
#include <string>
#include <tuple>
#include <utility>
#include <vector>

#include "coin/feed/fastfeed/feed.pb.h"
#include "coin2/base/log.h"
#include "coin2/base/time.h"
#include "coin2/exchange/feed.pb.h"

struct Kline {
  int64_t timestamp;
  fastfeed::proto::KlineInterval interval;
  int64_t open_timestamp;
  int64_t close_timestamp;
  double open;
  double close;
  double high;
  double low;
  double volume;
  double turnover;
  double buy_volume;
  double buy_turnover;

  void Clear() {
    timestamp = 0;
    interval = fastfeed::proto::KLINE_INTERVAL_INVALID;
    open_timestamp = 0;
    close_timestamp = 0;
    open = std::numeric_limits<double>::quiet_NaN();
    close = std::numeric_limits<double>::quiet_NaN();
    high = std::numeric_limits<double>::quiet_NaN();
    low = std::numeric_limits<double>::quiet_NaN();
    volume = std::numeric_limits<double>::quiet_NaN();
    turnover = std::numeric_limits<double>::quiet_NaN();
    buy_volume = std::numeric_limits<double>::quiet_NaN();
    buy_turnover = std::numeric_limits<double>::quiet_NaN();
  }
};

class Klines {
 public:
  enum KlineOption { SNAPSHOT, DELTA };

  using Iterator = std::deque<Kline>::const_iterator;
  using ReverseIterator = std::deque<Kline>::const_reverse_iterator;

  // get freezed kline
  std::optional<const Kline*> Get(unsigned int idx = 0) const {
    unsigned int real_index = idx;
    if (has_unfreezed_) real_index += 1;

    if (klines_.size() <= real_index) {
      return std::nullopt;
    } else {
      return std::optional(&(klines_.at(real_index)));
    }
  }

  // for freezed kline
  Kline* AddKline() {
    has_unfreezed_ = false;
    AddEmtpyKline();
    return (&klines_.front());
  }

  // for unfreezed kline
  std::optional<std::tuple<Kline*, bool>> AddKline(int64_t open_timestamp) {
    has_unfreezed_ = true;
    bool become_freezed = false;
    if (klines_.size() == 0) {
      AddEmtpyKline();
      // no kline become freezed kline
      become_freezed = false;
    } else if (open_timestamp < klines_.front().open_timestamp) {
      // incoming kline is out of date
      return std::nullopt;
    } else if (open_timestamp > klines_.front().open_timestamp) {
      // new kline come in, current kline is closed
      AddEmtpyKline();
      become_freezed = true;
    } else {
      // unfreezed kline update
      become_freezed = false;
    }

    // unfreezed kline update
    auto& kline = klines_.front();
    kline.open_timestamp = open_timestamp;
    return std::optional(std::make_tuple(&kline, become_freezed));
  }

  void Freeze() {
    has_unfreezed_ = false;
  }

  std::optional<const Kline*> GetUnfreezed() {
    if (!has_unfreezed_) {
      return std::nullopt;
    }

    return std::optional(&klines_.front());
  }

  // only iterate freeezed kline
  Iterator Begin() const {
    if (has_unfreezed_) {
      return std::next(klines_.begin());
    }

    return klines_.begin();
  }
  Iterator End() const { return klines_.end(); }

  // only iterate freeezed kline
  ReverseIterator RBegin() const { return klines_.rbegin(); }

  ReverseIterator REnd() const {
    if (has_unfreezed_) {
      return std::next(klines_.rend(), -1);
    }

    return klines_.rend();
  }

  uint32_t Size() const {
    if (klines_.empty()) {
      return 0;
    } else if (has_unfreezed_) {
      return klines_.size() - 1;
    }

    return klines_.size();
  }

  uint32_t DeltaSize() const {
    if (delta_count_ == 0) {
      return 0;
    }

    if (has_unfreezed_) {
      return delta_count_ - 1;
    }

    return delta_count_;
  }

  Iterator DeltaBegin() const {
    if (delta_count_ == 0) {
      return klines_.end();
    }

    if (has_unfreezed_) {
      return std::next(klines_.begin());
    }

    return klines_.begin();
  }

  Iterator DeltaEnd() const {
    if (delta_count_ == 0) {
      return klines_.end();
    }

    return std::next(klines_.begin(), delta_count_);
  }

  ReverseIterator DeltaRBegin() const {
    if (DeltaSize() == 0) {
      return klines_.rend();
    }

    return std::next(klines_.rbegin(), Size() - DeltaSize());
  }

  ReverseIterator DeltaREnd() const {
    if (DeltaSize() == 0) {
      return klines_.rend();
    }

    if (has_unfreezed_) {
      return std::next(klines_.rend(), -1);
    }

    return klines_.rend();
  }

  void SnapshotClear() {
    klines_.clear();
    delta_count_ = 0;
    option_ = SNAPSHOT;
  }

  void UpdateClear() {
    // no need clear, just keep same opration as snapshot
    option_ = DELTA;
  }

  KlineOption Type() const { return option_; }

 protected:
  void AddEmtpyKline() {
    klines_.push_front({});
    klines_.front().Clear();

    if (klines_.size() > MAX_DATA_) {
      klines_.resize(MAX_DATA_);
    }

    if (option_ == DELTA) {
      delta_count_++;
    }
  }

  std::deque<Kline> klines_;
  bool has_unfreezed_ = false;
  int delta_count_ = 0;  // kline only added in the front
  KlineOption option_ = SNAPSHOT;

  static constexpr int MAX_DATA_ = 2000;
};

inline std::ostream& operator<<(std::ostream& os, const Kline& kline) {
  os << "Kline (";
  os << "interval=" << KlineInterval_Name(kline.interval);
  os << ",open_timestamp=" << kline.open_timestamp;
  os << ",close_timestamp=" << kline.close_timestamp;
  os << std::setprecision(9);
  os << ",open=" << kline.open;
  os << ",high=" << kline.high;
  os << ",low=" << kline.low;
  os << ",close=" << kline.close;
  os << ",volume=" << kline.volume;
  os << ",turnover=" << kline.turnover;
  os << ",buy_volume=" << kline.buy_volume;
  os << ",buy_turnover=" << kline.buy_turnover;
  os << ")";
  return os;
}
